/**
 * 210824：升级修改【three.js r120-r131】
 */
let THREE=require('three');
import Utils from '../../general/utils';
import Collider from './collider';
import CONST from '../collision-const';

const DEFAULT_RECT={width:2048,height:2048};
const SELECT_DIRECTION=new THREE.Vector3(0,-1,0);

/**
 * 屏幕投射盒体
 * @param values {color,extents} color:THREE.Color,extents:[长,宽,高]
 * 注意：
 *      1.此类型需要指定用于投影的相机后才能生效
 *      2.此形状的包围盒是投影到屏幕后的包围盒（实为2D）
 *      3.此形状的包围球是投影到屏幕后的包围球（实为圆形范围）
 *      4.考虑到性能，并没有自动更新投影信息，检测之前务必调用[updateProjection]
 *      5.此类型形状只能与相同型形状进行碰撞检测，不要与其它形状混用
 */
export default class BoxScreenCollider extends Collider{
    constructor(values={}){
        super(values);
    }

    //---------------------------------对外属性及方法---------------------------------

    /**
     * 获取类型
     * @returns {string}
     */
    get colliderType(){
        return CONST.COLLIDER_TYPE_BOX_SCREEN;
    }

    /**
     * 获取投影相机
     * @returns {*|null}
     */
    get camera(){
        return this._camera;
    }

    /**
     * 设置投影相机
     * @param camera
     */
    set camera(camera){
        this._camera=camera;
    }

    /**
     * 获取包围盒（世界空间）
     * @returns {this}
     */
    get bounds(){
        return this._tB0.clone().applyMatrix4(this.matrixWorld);
    }

    /**
     * 获取包围盒
     * @returns {Box3}
     */
    get box(){
        return this._shape.clone();
    }

    /**
     * 获取包围球
     * @returns {Sphere}
     */
    get sphere(){
        return this._sphere.clone();
    }

    /**
     * 获取长宽高
     * @returns {*|null|Vector3}
     */
    get extents(){
        return this._props.extents;
    }

    /**
     * 设置长宽高
     * @param extents {array}
     */
    set extents(extents){
        let e=this._props.extents;
        if(e[0]===extents[0]&&e[1]===extents[1]&&e[2]===extents[2])return;
       this._setExtents(this._props.extents);
    }

    /**
     * 获取形状投影到屏幕上的凸壳（轮廓点集）
     * @returns {null|[]}
     */
    get outer(){
        return this._outer;
    }

    /**
     * 相机变化时调用（重新投影并计算包围体）
     */
    updateProjection(){
        if(!this._camera){
            console.warn('assign a camera for BoxScreenShape to active it!');
            return;
        }
        this._setProjection();
        this._shape.setFromPoints(this._prjVs);
        this._shape.getBoundingSphere(this._sphere);
    }

    /**
     * 销毁
     */
    dispose(){
        super.dispose();
        this._sphere=null;
        this._extVs=null;
        this._prjVs=null;
        this._outer=null;
        this._camera=null;
        this._tV0=null;
        this._tV1=null;
        this._tV2=null;
        this._sV0=null;
        this._breakDepth=null;
        this._tS0=null;
        this._invMat=null;
        this._ray=null;
        this._tB0=null;
        this._tS1=null;
    }

    //----------------------------------------------私有属性及方法---------------------------------------------------

    /**
     * 屏幕投影点的比较函数
     * @param a
     * @param b
     * @returns {boolean}
     * @private
     */
    _compare(a,b){
        return a.x===b.x&&a.y===b.y;
    }

    /**
     * 采集投影信息
     * @private
     */
    _setProjection(){
        for(let i=0;i<this._extVs.length;i++){
            this._tV0.copy(this._extVs[i]).applyMatrix4(this.matrixWorld);
            Utils.worldPointToScreen(this._camera,DEFAULT_RECT,this._tV0,this._tV1);
            if(Utils.inElements(this._tV1,this._prjVs,this._compare,false))continue;
            this._prjVs[i].copy(this._tV1);
        }
        this._outer.length=0;
        this._breakDepth=0;
        this._selectStart();
        this._selectOuter(SELECT_DIRECTION,this._sV0);
    }

    /**
     * 获取投影后凸壳的轮廓点集
     * @param direction
     * @param previous
     * @private
     */
    _selectOuter(direction,previous){
        this._breakDepth++;
        if(this._breakDepth>10000){
            console.warn('max iteration has been reached, failed to get outer points!');
            return;
        }
        this._tV0.copy(direction);
        let maxDot=Number.NEGATIVE_INFINITY;
        let maxIdx=-1;
        for(let i=0;i<this._prjVs.length;i++){
            let v=this._prjVs[i];
            if(this._compare(v,previous))continue;
            this._tV1.subVectors(v,previous).normalize();
            let dot=this._tV0.dot(this._tV1);
            if(maxDot<dot){
                maxDot=dot;
                maxIdx=i;
                this._tV2.copy(this._tV1);
            }
        }
        if(this._outer[0]){
            if(this._compare(this._outer[0],this._prjVs[maxIdx]))return;
        }
        this._outer.push(this._prjVs[maxIdx]);
        this._selectOuter(this._tV2,this._prjVs[maxIdx]);
    }

    /**
     * 获取计算凸壳所使用的开始点
     * @private
     */
    _selectStart(){
        this._sV0.set(Number.POSITIVE_INFINITY,Number.NEGATIVE_INFINITY,0);
        for(let i=0;i<this._prjVs.length;i++){
            let p=this._prjVs[i];
            if(this._sV0.x>p.x)this._sV0.x=p.x;
            if(this._sV0.y<p.y)this._sV0.y=p.y;
        }
    }

    /**
     * 绘制调试
     * @private
     */
    _createDebug(){
        if(this._debug)return;
        let box=new THREE.Box3();

        this._setExtents(this._props.extents);

        box.setFromPoints(this._extVs);
        this._debug=new THREE.Box3Helper(box,this._props.color);
        this.add(this._debug);
    }

    /**
     * 销毁调试
     * @private
     */
    _destroyDebug(){
        if(!this._debug)return;
        this.remove(this._debug);
        this._debug.geometry.dispose();
        this._debug.material.dispose();
        this._debug=null;
    }

    /**
     * 初始化属性
     * @private
     */
    _initProps(){
        super._initProps();
        this._props.extents=this._values.extents||[0.5,0.5,0.5];
    }

    /**
     * 初始化变量
     * @private
     */
    _initVariable(){
        super._initVariable();
        this._sphere=new THREE.Sphere();
        this._extVs=[];
        this._prjVs=[];
        this._outer=[];
        this._camera=null;
        this._tV0=new THREE.Vector3();
        this._tV1=new THREE.Vector3();
        this._tV2=new THREE.Vector3();
        this._sV0=new THREE.Vector3();
        this._breakDepth=0;
        this._tS0=new THREE.Sphere();
        this._invMat=new THREE.Matrix4();
        this._ray=new THREE.Ray();
        this._tB0=new THREE.Box3();
        this._tS1=new THREE.Sphere();
    }

    /**
     * 初始化形状
     * @private
     */
    _initShape(){
        for(let i=0;i<8;i++){
            this._extVs.push(new THREE.Vector3());
            this._prjVs.push(new THREE.Vector3());
        }

        this._setExtents(this._props.extents);

        this._shape=new THREE.Box3();
    }

    /**
     * 设置box长宽高
     * @param extents
     * @private
     */
    _setExtents(extents){
        let x=extents[0];
        let y=extents[1];
        let z=extents[2];
        this._extVs[0].set(-x,-y,z);
        this._extVs[1].set(x,-y,z);
        this._extVs[2].set(x,-y,-z);
        this._extVs[3].set(-x,-y,-z);
        this._extVs[4].set(-x,y,z);
        this._extVs[5].set(x,y,z);
        this._extVs[6].set(x,y,-z);
        this._extVs[7].set(-x,y,-z);
        this._tB0.setFromPoints(this._extVs);
        this._tB0.getBoundingSphere(this._tS0);
    }

    /**
     * 初始化射线检测函数
     * @private
     */
    _initRaycast() {
        this._raycast=(raycaster,intersects,target)=>{
                let object=this;
                let matrixWorld = this.matrixWorld;
                this._tS1.copy(this._tS0).applyMatrix4(matrixWorld);
                if ( raycaster.ray.intersectsSphere( this._tS1 ) === false ) return;
                // this._invMat.getInverse( matrixWorld );20210824
                this._invMat.copy( matrixWorld ).invert();
                this._ray.copy( raycaster.ray ).applyMatrix4( this._invMat );
                let intersectPoint=new THREE.Vector3();
                if(this._ray.intersectBox(this._tB0,intersectPoint)){
                    intersectPoint.applyMatrix4(matrixWorld);
                    let distance=raycaster.ray.origin.distanceTo(intersectPoint);
                    intersects.push({
                        distance:distance,
                        distanceToRay:0,
                        index: null,
                        face: null,
                        point:intersectPoint,
                        object:target?target:object,
                        shape:object,
                    });
                }
            };
    }
}